////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   ParticleEmitter.cpp
//  Created:     18/7/2003 by Timur.
//  Modified:    17/3/2005 by Scott Peter
//  Compilers:   Visual Studio.NET
//  Description: 
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "ParticleEmitter.h"
#include "ParticleContainer.h"
#include "ParticleSubEmitter.h"
#include "ICryAnimation.h"
#include "Particle.h"
#include "FogVolumeRenderNode.h"
#include "ISound.h"
#include "IThreadTask.h"
#include "STLGlobalAllocator.h"
#include "StringUtils.h"

#include <SpuUtils.h>
#include <IJobManager_JobDelegator.h>

#define fUNSEEN_EMITTER_RESET_TIME							float(5.f)		// Max time to wait before freeing unseen emitter resources
#define fSOUND_RANGE_START_BUFFER								float(2.f)		// Distance to max sound range in order to start sound
#define fVELOCITY_SMOOTHING_TIME								float(0.125f)	// Interval to smooth computed entity velocity

/*
	Scheme for Emitter updating & bounding volume computation.

	Whenever possible, we update emitter particles only on viewing.
	However, the bounding volume for all particles must be precomputed, 
	and passed to the renderer,	for visibility. Thus, whenever possible, 
	we precompute a Static Bounding Box, loosely estimating the possible
	travel of all particles, without actually updating them.

	Currently, Dynamic BBs are computed for emitters that perform physics,
	or are affected by non-uniform physical forces.
*/


SPU_ENTRY(UpdateParticles)
void UpdateParticlesBatch( int seed, UpdateParticlesEmitterBatch batch )
{




	
	for( uint32 i = 0 ; i < UpdateParticlesEmitterBatch::MAX_BATCHES && batch.batch[i] != NULL ; ++i )
	{
		SPU_MAIN_PTR(batch.batch[i])->UpdateAllParticleContainerSpu();
	}
}

void CParticleEmitter::UpdateAllParticleContainerSpu()
{
	for_all_ptrs (CParticleContainer, pCont, m_Containers)
	{
		// we currently have way more time on SPU than on PPU, so compute all UpdateParticles speculative (to not have first created emitters on PPU)
		if (pCont->NeedSpuUpdate())
		{			
			pCont->SetNeedSpuUpdate(false);
			SPU_MAIN_PTR( static_cast<CParticleContainer*>(pCont) )->UpdateParticles();
		}
	}
}

void CParticleEmitter::SyncUpdateParticlesSPU()
{
	if (m_pUpdateParticlesSPUState)
	{
		PARTICLE_LIGHT_SYNC_PROFILER();
		gEnv->GetJobManager()->WaitForJob(*m_pUpdateParticlesSPUState);	
	}
}

//////////////////////////////////////////////////////////////////////////
EEmitterState CParticleEmitter::GetState() const
{
	if (m_fAge <= m_fDeathAge)
		// Still has active particles.
		return eEmitter_Active;

	if (m_fAge > m_fEndAge)
		// Emitter deactivated.
		return eEmitter_Dead;
	if (m_fAge < 0.f)
		// Emitter paused.
		return eEmitter_Dormant;

	if (GetSpawnParams().fPulsePeriod > 0.f)
		// Between pulses.
		return eEmitter_Dormant;
	else
		// Natural death.
		return eEmitter_Dead;
}

void CParticleEmitter::UpdateState()
{
	FUNCTION_PROFILER_SYS(PARTICLE);

	uint32 nEnvFlags = GetEnvFlags();
	Vec3 vPos = GetLocation().t;

	bool bUpdateBounds = (nEnvFlags & REN_ANY) && (
		m_bbWorld.IsReset() ||
		(nEnvFlags & EFF_DYNAMIC_BOUNDS) || 
		#ifndef _RELEASE
				(GetCVars()->e_ParticlesDebug & AlphaBit('d')) ||
		#endif
		m_fAge <= m_fBoundsStableAge
	);

	IF (!(m_PhysEnviron.m_nNonUniformFlags & EFF_LOADED), 0)
	{
		// For initial bounds computation, just get physical environment at the origin.
		m_PhysEnviron.GetPhysAreas( CParticleManager::Instance()->GetPhysEnviron(), AABB(vPos), m_nEnvFlags, 
			this, Get3DEngine()->GetVisAreaFromPos(vPos) != NULL, false, vPos );
		bUpdateBounds = true;
	}

	bool bUpdateState = bUpdateBounds || m_fAge >= m_fStateChangeAge;
	if (bUpdateState)
	{
		m_fStateChangeAge = fHUGE;

		// Update states and bounds.
		if (!m_Containers.empty())
			UpdateContainers();
		else if (bUpdateBounds)
			AllocEmitters();

		if (bUpdateBounds && !(m_nEmitterFlags & ePEF_Nowhere))
		{
			m_VisEnviron.Update(GetLocation().t, m_bbWorld);
			m_PhysEnviron.GetPhysAreas( CParticleManager::Instance()->GetPhysEnviron(), m_bbWorld, m_nEnvFlags, 
				this, m_VisEnviron.OriginIndoors(), true, vPos );
		}
	}
}

void CParticleEmitter::Activate( bool bActive )
{ 
	if (bActive)
		Start();
	else
		End();

	UpdateTimes();
}

void CParticleEmitter::Restart()
{ 
	Reset();
	m_fAge = max(m_fAge, 0.f);
	m_fEndAge = m_fCollideAge = fHUGE;
	UpdateTimes();
}

void CParticleEmitter::UpdateTimes()
{
	if (m_Containers.empty() && m_pTopEffect)
	{
		// Set death age to effect maximum.
		m_fDeathAge = m_pTopEffect->GetMaxEffectLife( FEffectLife().bAllChildren(1).fEmitterMaxLife(m_fEndAge).bParticleLife(1) );
	}
	else
	{
		m_fDeathAge = 0.f;
		m_fStateChangeAge = fHUGE;

		// Update subemitter timings, and compute exact death age.
		for_all_ptrs (CParticleContainer, c, m_Containers)
		{
			c->UpdateContainerLife();
			m_fDeathAge = max(m_fDeathAge, c->GetContainerLife());
			m_fStateChangeAge = min(m_fStateChangeAge, c->GetContainerLife());
		}
	}

	m_fAgeLastRendered = min(m_fAgeLastRendered, m_fAge);
	UpdateResetAge();
}

void CParticleEmitter::Kill()
{
	Reset();
	if (m_fDeathAge >= 0.f)
	{
		CParticleSource::Kill();
		m_fDeathAge = -fHUGE;
		CParticleManager::Instance()->OnDeleteEmitter(this);
	}
}

void CParticleEmitter::UpdateResetAge()
{ 
	m_fResetAge = m_fAge + fUNSEEN_EMITTER_RESET_TIME; 
}

void CParticleEmitter::Prime()
{
	if (GetAge() >= GetEndAge())
		return;

	float fEqTime = m_pTopEffect->GetEquilibriumAge(true);
	Start(-fEqTime);
}

IMaterial* CParticleEmitter::GetMaterial(Vec3* pHitPos)
{
	if (m_pMaterial)
		return m_pMaterial;
	if (!m_Containers.empty())
		return m_Containers.front().GetParams().pMaterial;
	return NULL;
}

void CParticleEmitter::UpdateEmitCountScale()
{
	m_fEmitCountScale = m_SpawnParams.fCountScale * GetCVars()->e_ParticlesLod;
	float fExt = GetExtent(GetEmitGeom(), m_SpawnParams.eAttachForm);
	if (m_SpawnParams.bCountPerUnit)
	{
		// Count multiplied by geometry extent.
		m_fEmitCountScale *= fExt;
		m_fEmitCountScale *= ScaleExtent(m_SpawnParams.eAttachForm, GetLocation().s);
	}
}

CParticleContainer* CParticleEmitter::AddContainer(CParticleContainer* pParentContainer, const CParticleEffect* pEffect)
{
	void* pMem = m_Containers.push_back_new();
	if (pMem)
		return new(pMem) CParticleContainer(pParentContainer, this, pEffect);
	return NULL;
}

//////////////////////////////////////////////////////////////////////////
// CParticleEmitter implementation.
////////////////////////////////////////////////////////////////////////
CParticleEmitter::CParticleEmitter( const IParticleEffect* pEffect, const PivotLocation& loc, uint32 uEmitterFlags, const SpawnParams* pSpawnParams )
	: m_fMaxParticleSize(0.f)
	, m_fEmitCountScale(1.f)
	, m_fViewDistRatio(1.f)
	, m_timeLastUpdate(GetParticleTimer()->GetFrameStartTime())
	, m_fDeathAge(0.f)
	, m_fStateChangeAge(0.f)
	, m_fAgeLastRendered(0.f)
	, m_fBoundsStableAge(0.f)
	, m_fResetAge(fUNSEEN_EMITTER_RESET_TIME)
	, m_nEmitterFlags(uEmitterFlags)
	, m_nContainerCount(0)
	, m_nNearestCubemapId(0)
	, m_nLastCubemapUpdateFrameId(-999)
	, m_pUpdateParticlesSPUState(NULL)
{
	m_nEntityId = 0;
	m_nEntitySlot = -1;
	m_nEnvFlags = 0;
	m_bbWorld.Reset();

	CParticleSource::SetLocation(loc);
	m_vLocalPivot = loc.pivot;

#ifdef SHARED_GEOM
	m_nInstInfosUsed = 0;
#endif

	if (pSpawnParams)
		m_SpawnParams = *pSpawnParams;

	GetInstCount(GetRenderNodeType())++;	

	SetEffect(pEffect);
}

//////////////////////////////////////////////////////////////////////////
CParticleEmitter::~CParticleEmitter() 
{	
	Get3DEngine()->FreeRenderNodeState(this); // Also does unregister entity.
	GetInstCount(GetRenderNodeType())--;
}

//////////////////////////////////////////////////////////////////////////
void CParticleEmitter::Register(bool b)
{	
	if (!b)
	{
		if (m_nEmitterFlags & ePEF_Registered)
		{
			Get3DEngine()->UnRegisterEntity(this);			
			m_nEmitterFlags &= ~ePEF_Registered;
		}
	}
	else
	{
		// Register top-level render node if applicable.
		if (m_nEmitterFlags & ePEF_Nowhere)
			Get3DEngine()->UnRegisterEntity(this);		
		else if (!(m_nEmitterFlags & ePEF_BoundsCurrent))
		{
			if (m_nEmitterFlags & ePEF_Registered)
				Get3DEngine()->UnRegisterEntity(this);			
			if (!m_bbWorld.IsReset())
			{
				// Register node.
				// Normally, use emitter's designated position for visibility.
				// However, if all bounds are computed dynamically, they might not contain the origin, so use bounds for visibility.
				if (m_bbWorld.IsContainPoint(GetPos()))
					SetRndFlags(ERF_REGISTER_BY_POSITION, true);
				else
					SetRndFlags(ERF_REGISTER_BY_POSITION, false);
				Get3DEngine()->RegisterEntity(this);
				m_nEmitterFlags |= ePEF_Registered;
			}
			else
				m_nEmitterFlags &= ~ePEF_Registered;
			m_nEmitterFlags |= ePEF_BoundsCurrent;
		}
	}
}

//////////////////////////////////////////////////////////////////////////
string CParticleEmitter::GetDebugString(char type) const
{
	string s = GetName();
	if (type == 's')
	{
		// Serialization debugging.
		IEntity* pEntity = GetEntity();
		if (pEntity)
			s += string().Format(" entity=%s slot=%d", pEntity->GetName(), m_nEntitySlot);
		if (IsIndependent())
			s += " indep";
		switch (GetState())
		{
			case eEmitter_Active: 
				s += " Active";
				break;
			case eEmitter_Dormant:
				s += " Dormant";
				break;
			case eEmitter_Dead:
				s += " Dead";
				break;
		}
	}
	else
	{
		SParticleCounts counts;
		GetCounts(counts);
		s += string().Format(" E=%.0f P=%.0f R=%0.f", counts.EmittersActive, counts.ParticlesActive, counts.ParticlesRendered);
	}
	
	s += string().Format(" age=%.3f", GetAge());
	return s;
}

//////////////////////////////////////////////////////////////////////////
void CParticleEmitter::AddEffect( CParticleContainer* pParentContainer, CParticleSubEmitter* pParentEmitter, const CParticleEffect* pEffect, bool bUpdate )
{
	if (!pEffect)
		return;

	CParticleContainer* pContainer = pParentContainer;
	const ResourceParticleParams* pParams = &pEffect->GetParams();

	// All descendents of an indirect effect are also indirect effects, with the same parent.
	if (!pParams->eSpawnIndirection && pParentContainer && pParentContainer->GetParent())
	{
		pParentContainer = pParentContainer->GetParent();
	}

	// Add if playable in current config.
	if (pEffect->IsActive())
	{
		if (IsIndependent() && pParams->IsImmortal())
		{
#ifndef _RELEASE
			// Do not allow immortal effects on independent emitters.
			static std::set<const ParticleParams*, std::less<const ParticleParams*>, stl::STLGlobalAllocator<const ParticleParams*> > s_setWarnings;
			if (s_setWarnings.insert(pParams).second)
			{
				Warning("Ignoring spawning of immortal independent particle sub-effect %s",
								(pEffect ? pEffect->GetName() : "[programmatic]"));
			}
#endif
		}
		else
		{
			if (bUpdate)
			{
				// Look for existing container. 
				// Note: Inactive/disabled effects will not be removed, so they can be disabled/re-enabled when editing.
				// They will be skipped on playback.
				for_all_ptrs (CParticleContainer, c, m_Containers)
				{
					if (c && !c->IsUsed() && c->GetParent() == pParentContainer && c->GetEffect() == pEffect)
					{
						pContainer = c;
						pContainer->SetUsed(true);
						pContainer->OnEffectChange();
						pContainer->InvalidateStaticBounds();
						break;
					}
				}
			}
			if (pContainer == pParentContainer)
			{
				// Add new container
				pContainer = AddContainer(pParentContainer, pEffect);
				if (pContainer && !pContainer->GetParent())
				{
					// This is not a second-gen container, so it will need only a single SubEmitter - we can
					// allocate it here
					pParentEmitter = pContainer->AddEmitter( this, pParentEmitter );
				}
			}
		}
	}

	// Recurse effect tree.
	if (pEffect)
	{
		for (int i = 0, n = pEffect->GetChildCount(); i < n; i++)
		{
			CParticleEffect const* pChild = static_cast<const CParticleEffect*>(pEffect->GetChild(i));
			if(pChild)
			{
				if (!pChild->GetParams().eSpawnIndirection)
				{
					AddEffect( pParentContainer, pParentEmitter, pChild, bUpdate );
				}
				else if (pContainer)
				{
					AddEffect( pContainer, pParentEmitter, pChild, bUpdate );
				}
			}
		}
	}
}

void CParticleEmitter::SetEffect( IParticleEffect const* pIEffect )
{
	FUNCTION_PROFILER_SYS(PARTICLE);

	CParticleEffect const* pEffect = static_cast<CParticleEffect const*>(pIEffect);

	if (m_pTopEffect.get() != pEffect)
	{
		// Clear any existing emitters only if changing effect.
		Reset();
		m_pTopEffect = &non_const(*pEffect);
		if (!pEffect)
			return;
	}

	if (pEffect)
		pEffect->LoadResources(true);

	if (m_Containers.empty())
	{
		// Create new emitters.
		AddEffect(0, 0, pEffect, false);
	}
	else
	{
		// Update existing emitters.
		for_all_ptrs (CParticleContainer, c, m_Containers)
			c->SetUsed(false);

		AddEffect(0, 0, pEffect, true);

		// Clear in reverse order, children before parents.
		for_rev_all_ptrs (CParticleContainer, c, m_Containers)
		{
			if (!c->IsUsed())
				m_Containers.erase(c);
		}
	}

	m_nEnvFlags = 0;
	m_nRenObjFlags = 0;
	m_fMaxParticleSize = 0.f;
	m_nContainerCount = m_Containers.size();

	float fSortBias = 0.f;
	for_all_ptrs (CParticleContainer, c, m_Containers)
	{
		m_nEnvFlags |= c->GetEnvironmentFlags();
		m_nRenObjFlags |= c->GetParams().nRenObjFlags.On;
		m_fMaxParticleSize = max(m_fMaxParticleSize, c->GetParams().GetMaxParticleSize() * c->GetParams().fViewDistanceAdjust);
		if (c->GetParams().fMinPixels)
			m_fMaxParticleSize = fHUGE;
		c->ModifySortBias(fSortBias);
		fSortBias += 0.01f;
	}

	m_bbWorld.Reset();
	m_VisEnviron.Invalidate();
	m_PhysEnviron.Clear();

	// Force state update.
	m_fStateChangeAge = -fHUGE;
	m_nEmitterFlags |= ePEF_NeedsEntityUpdate;
}

void CParticleEmitter::CreateIndirectEmitters( CParticleContainer* pCont, CParticle* pParentPart )
{
	CParticleSubEmitter* pParentEmitter = 0;

	for_all_ptrs (CParticleContainer, pChild, m_Containers)
	{
		if (pChild->GetParent() == pCont)
		{
			// Indirect container for this emitter.
			CParticleSubEmitter* pEmitter = pChild->AddEmitter(pParentPart, pParentEmitter);
			if (!pEmitter)
				break;
			if (pChild->GetEffect()->GetParent() == pCont->GetEffect())
				pParentEmitter = pEmitter;
		}
	}
}

void CParticleEmitter::SetLocation( const PivotLocation& loc)
{
	if (!QuatTS::IsEquivalent(GetLocation(), loc))
	{
		InvalidateStaticBounds();
		m_VisEnviron.Invalidate();
	}

	if (!(m_nEmitterFlags & ePEF_HasPhysics))
	{
		// Infer velocity from movement, smoothing over time.
		if (float fStep = GetParticleTimer()->GetFrameTime())
		{
			Velocity3 vv;
			vv.FromDelta( QuatT(GetLocation().q, GetLocation().t), QuatT(loc.q, loc.t), max(fStep, fVELOCITY_SMOOTHING_TIME) );
			GetVelocity() += vv;
		}	
	}

	CParticleSource::SetLocation(loc);
	m_vLocalPivot = loc.pivot;

	if (IEntity* pEntity = GetEntity())
	{
		// Set pivot to entity.
		while (pEntity->GetParent())
			pEntity = pEntity->GetParent();
		m_vLocalPivot = loc.GetInverted() * pEntity->GetWorldPos();
	}
}

void CParticleEmitter::ResetUnseen()
{
	if (!(GetEnvFlags() & (EFF_DYNAMIC_BOUNDS | EFF_ANY)) && !(GetCVars()->e_ParticlesDebug & AlphaBit('k')))
	{
		// Can remove all emitter structures.
		m_Containers.clear();
	}
	else
	{
		// Some containers require update every frame; reset the others.
		for_rev_all_ptrs (CParticleContainer, c, m_Containers)
			if (!c->NeedsDynamicBounds())
				c->Reset();
	}
}

void CParticleEmitter::AllocEmitters()
{
	// (Re)alloc cleared emitters as needed.
	if (m_Containers.empty())
	{
		SyncUpdateParticlesSPU();
		AddEffect(0, 0, m_pTopEffect, false);
		UpdateContainers();
	}
}

void CParticleEmitter::UpdateContainers()
{
	AABB bbPrev = m_bbWorld;
	m_bbWorld.Reset();
	m_fDeathAge = 0.f;
	for_all_ptrs (CParticleContainer, c, m_Containers)
	{
		c->UpdateState();
		float fContainerLife = c->GetContainerLife();
		m_fDeathAge = max(m_fDeathAge, fContainerLife);
		if (fContainerLife > m_fAge)
			m_fStateChangeAge = min(m_fStateChangeAge, fContainerLife);

		if (c->GetEnvironmentFlags() & REN_ANY)
			m_bbWorld.Add(c->GetBounds());
	}
	if (!IsEquivalent(bbPrev, m_bbWorld))
		m_nEmitterFlags &= ~ePEF_BoundsCurrent;
}

bool CParticleEmitter::IsInstant() const
{
	return GetEndAge() + GetSpawnParams().fPulsePeriod == 0.f;
}

bool CParticleEmitter::IsImmortal() const
{
	return GetSpawnParams().fPulsePeriod > 0.f || m_pTopEffect->GetMaxEffectLife( FEffectLife().bAllChildren(1).fEmitterMaxLife(fHUGE).bParticleLife(1) ) > fHUGE*0.5f;
}

void CParticleEmitter::SetEmitGeom(GeomRef const& geom)
{
	if (CParticleSource::SetEmitGeom(geom))
		InvalidateStaticBounds();
	if (geom && m_SpawnParams.eAttachType)
		m_nEmitterFlags |= ePEF_HasAttachment;
	else
		m_nEmitterFlags &= ~ePEF_HasAttachment;
}

void CParticleEmitter::SetSpawnParams( SpawnParams const& spawnParams, GeomRef geom )
{
	if (ZeroIsHuge(spawnParams.fPulsePeriod) < 0.1f)
		Warning("Particle emitter (effect %s) PulsePeriod %g too low to be useful", 
			GetName(), spawnParams.fPulsePeriod);
	if (spawnParams.eAttachType != m_SpawnParams.eAttachType ||
			spawnParams.fSizeScale != m_SpawnParams.fSizeScale ||
			spawnParams.fSpeedScale != m_SpawnParams.fSpeedScale)
		InvalidateStaticBounds();

	m_SpawnParams = spawnParams;
	NormalizeEmitGeom(geom, spawnParams.eAttachType);
	SetEmitGeom(geom);
}

IEntity* CParticleEmitter::GetEntity() const
{
	if (m_nEntityId)
	{
		IEntity* pEntity = gEnv->pEntitySystem->GetEntity(m_nEntityId);
		assert(pEntity);
		return pEntity;
	}
	return NULL;
}

bool GetPhysicalVelocity( Velocity3& Vel, IEntity* pEnt )
{
	if (pEnt)
	{
		if (IPhysicalEntity* pPhysEnt = pEnt->GetPhysics())
		{
			pe_status_dynamics dyn;
			if (pPhysEnt->GetStatus(&dyn))
			{
				Vel.vLin = dyn.v;
				Vel.vRot = dyn.w;
				return true;
			}
		}
		if (pEnt = pEnt->GetParent())
			return GetPhysicalVelocity(Vel, pEnt);
	}
	return false;
}

void CParticleEmitter::OnEntityEvent( IEntity* pEntity, SEntityEvent const& event )
{
	switch (event.event)
	{
		case ENTITY_EVENT_ATTACH_THIS:
		case ENTITY_EVENT_DETACH_THIS:
		case ENTITY_EVENT_LINK:
		case ENTITY_EVENT_DELINK:
		case ENTITY_EVENT_ENABLE_PHYSICS:
		case ENTITY_EVENT_PHYSICS_CHANGE_STATE:
			m_nEmitterFlags |= ePEF_NeedsEntityUpdate;
			GetVelocity() = ZERO;
			break;
	}
}

void CParticleEmitter::UpdateFromEntity() 
{
	FUNCTION_PROFILER_SYS(PARTICLE);

	m_nEmitterFlags &= ~(ePEF_HasPhysics | ePEF_HasTarget | ePEF_HasAttachment);

	// Get emitter entity.
	IEntity* pEntity = GetEntity();

	// Set external target.
	if (!m_Target.bPriority)
	{
		ParticleTarget target;
		if (pEntity)
		{
			for (IEntityLink* pLink = pEntity->GetEntityLinks(); pLink; pLink = pLink->next)
			{
				if (!stricmp(pLink->name, "Target") || !strnicmp(pLink->name, "Target-", 7))
				{
					if (IEntity* pTarget = gEnv->pEntitySystem->GetEntity(pLink->entityId))
					{
						target.bTarget = true;
						target.vTarget = pTarget->GetPos();

						Velocity3 Vel(ZERO);
						GetPhysicalVelocity(Vel, pTarget);
						target.vVelocity = Vel.vLin;

						AABB bb;
						pTarget->GetLocalBounds(bb);
						target.fRadius = max(bb.min.len(), bb.max.len());
						m_nEmitterFlags |= ePEF_HasTarget;
						break;
					}
				}
			}
		}

		if (target.bTarget != m_Target.bTarget || target.vTarget != m_Target.vTarget)
			InvalidateStaticBounds();
		m_Target = target;
	}

	bool bShadows = (GetEnvFlags() & REN_CAST_SHADOWS) != 0;

	// Get entity of attached parent.
	if (pEntity)
	{
		if (!(pEntity->GetFlags() & ENTITY_FLAG_CASTSHADOW))
			bShadows = false;

		if (pEntity->GetPhysics())
			m_nEmitterFlags |= ePEF_HasPhysics;

		if (pEntity->GetParent())
			pEntity = pEntity->GetParent();

		if (pEntity->GetPhysics())
			m_nEmitterFlags |= ePEF_HasPhysics;

		const SpawnParams& spawn = GetSpawnParams();
		if (spawn.eAttachType != GeomType_None)
		{
			// If entity attached, find attached physics and geometry on parent.
			GeomRef geom;

			int nStart = m_nEntitySlot < 0 ? 0 : m_nEntitySlot;
			int nEnd = m_nEntitySlot < 0 ? 	pEntity->GetSlotCount() : m_nEntitySlot+1;
			for (int nSlot = nStart; nSlot < nEnd; nSlot++)
			{
				SEntitySlotInfo slotInfo;
				if (pEntity->GetSlotInfo( nSlot, slotInfo ))
				{
					geom.m_pStatObj = slotInfo.pStatObj;
					geom.m_pChar = slotInfo.pCharacter;
					NormalizeEmitGeom(geom, spawn.eAttachType);

					if (geom)
					{
						if (slotInfo.pWorldTM)
							SetMatrix(*slotInfo.pWorldTM);
						break;
					}
				}
			}

			// Set physical entity as well.
			if (spawn.eAttachType == GeomType_Physics && !geom.m_pPhysEnt)
				geom.m_pPhysEnt = pEntity->GetPhysics();

			SetEmitGeom(geom);
		}
	}

	SetRndFlags(ERF_CASTSHADOWMAPS, bShadows);
}

void CParticleEmitter::GetLocalBounds( AABB& bbox )
{
	if (!m_bbWorld.IsReset())
	{
		bbox.min = m_bbWorld.min - GetLocation().t;
		bbox.max = m_bbWorld.max - GetLocation().t;
	}
	else
	{
		bbox.min = bbox.max = Vec3(0);
	}
}

void CParticleEmitter::Update()
{
	FUNCTION_PROFILER_SYS(PARTICLE);

	m_fAge += (GetParticleTimer()->GetFrameStartTime() - m_timeLastUpdate).GetSeconds();
	m_timeLastUpdate = GetParticleTimer()->GetFrameStartTime();

	if (m_SpawnParams.fPulsePeriod > 0.f && m_fAge < GetEndAge())
	{
		// Apply external pulsing (restart).
		float fPast = m_fAge - m_SpawnParams.fPulsePeriod;
		if (fPast >= 0.f)
		{
			fPast = fmod(fPast, m_SpawnParams.fPulsePeriod);
			Start(-fPast);
			UpdateTimes();
		}
	}
	else if (m_fAge > m_fDeathAge)
		return;

	#ifdef SHARED_GEOM
		m_nInstInfosUsed = 0;
	#endif

	if (m_nEmitterFlags & (ePEF_NeedsEntityUpdate | ePEF_HasTarget | ePEF_HasAttachment))
	{
		m_nEmitterFlags &= ~ePEF_NeedsEntityUpdate;
		UpdateFromEntity();
	}

	if (m_fAge > m_fResetAge)
	{
		ResetUnseen();
		m_fResetAge = fHUGE;
	}

	// Update velocity
	Velocity3 Vel;
	if ((m_nEmitterFlags & ePEF_HasPhysics) && GetPhysicalVelocity(Vel, GetEntity()))
	{
		// Get velocity from physics.
		GetVelocity() = Vel;
	}
	else
	{
		// Decay velocity, using approx exponential decay, so we reach zero soon.
		float fDecay = max(1.f - GetParticleTimer()->GetFrameTime() / fVELOCITY_SMOOTHING_TIME, 0.f);
		GetVelocity() *= fDecay;
	}

	if ((GetEnvFlags() & REN_BIND_CAMERA) && gEnv->pSystem)
	{
		SetMatrix(gEnv->pSystem->GetViewCamera().GetMatrix());
	}

	UpdateEmitCountScale();

	// Update emitter state and bounds.
	UpdateState();
}

void CParticleEmitter::UpdateEffects()
{	
	FUNCTION_PROFILER_SYS(PARTICLE);

	const uint32 nEffectFlags = EFF_ANY & ~(EFF_SOUND * !m_SpawnParams.bEnableSound);

	for_all_ptrs (CParticleContainer, c, m_Containers)
	{
		if (c->GetEnvironmentFlags() & nEffectFlags)
			c->UpdateEffects(nEffectFlags);
	}
}

void CParticleEmitter::EmitParticle( const EmitParticleData* pData )
{
	AllocEmitters();
	if (!m_Containers.empty())
	{
		m_Containers.front().EmitParticle(pData);
		UpdateResetAge();
		m_fStateChangeAge = m_fAge;
	}
}

void CParticleEmitter::SetEntity( IEntity* pEntity, int nSlot )
{
	m_nEntityId = pEntity ? pEntity->GetId() : 0;
	m_nEntitySlot = nSlot;
	m_nEmitterFlags |= ePEF_NeedsEntityUpdate;

	for_all_ptrs (CParticleContainer, c, m_Containers) 
		if (CParticleSubEmitter* e = c->GetDirectEmitter())
			e->ResetLoc();
}

void CParticleEmitter::Render( SRendParams const& RenParams )
{
	SyncUpdateParticlesSPU();

	FUNCTION_PROFILER_SYS(PARTICLE);
	PARTICLE_LIGHT_PROFILER();

	IF (!GetCVars()->e_Particles, 0)
		return;	

	uint32 nEnvFlags = GetEnvFlags();

	IF (m_nRenderStackLevel > 0 && (nEnvFlags & REN_BIND_CAMERA), 0)
		// Render only in main camera.
		return; 

	if (m_fAge > m_fDeathAge)
		return;
	
	AllocEmitters();

	// Check combined emitter flags.
	uint32 nRenFlags = REN_SPRITE | REN_GEOMETRY;
	uint32 nRenRequiredFlags = 0;

	if (Get3DEngine()->IsRenderingIntoShadowMap())
	{
		nRenRequiredFlags |= REN_CAST_SHADOWS;
	}
	else if (!(m_nEmitterFlags & ePEF_Nowhere))
	{
		nRenFlags |= REN_DECAL | REN_REFRACTION;
		if (!m_nRenderStackLevel)
			nRenFlags |= REN_LIGHTS;
	}

	nRenFlags &= nEnvFlags;
	if (!nRenFlags)
		return;

	uint32 nRenRequiredCheckFlags = (nRenRequiredFlags | REN_REFRACTION) & ~nRenFlags;
	if ((nRenRequiredFlags & nEnvFlags) != nRenRequiredFlags)
		return;

	SPartRenderParams PartParams;
	ZeroStruct(PartParams);	

	if (!Get3DEngine()->IsRenderingIntoShadowMap())
	{
		m_fAgeLastRendered = GetAge();
		UpdateResetAge();
	}

	ColorF fogVolumeContrib;
	CFogVolumeRenderNode::TraceFogVolumes( GetPos(), fogVolumeContrib );
	PartParams.m_nFogVolumeContribIdx = m_pRenderer->PushFogVolumeContribution( fogVolumeContrib );

	PartParams.m_nRenFlags = nRenFlags;
	PartParams.m_pCamera = &GetRenderer()->GetCamera();
	PartParams.m_fCamDistance = GetPos().GetDistance(PartParams.m_pCamera->GetPosition());
	PartParams.m_fMaxAngularDensity = GetMaxAngularDensity(*PartParams.m_pCamera);
	PartParams.m_fHDRDynamicMultiplier = GetRenderer()->EF_Query(EFQ_HDRModeEnabled) ? Get3DEngine()->GetHDRDynamicMultiplier() : 1.f;

	PartParams.m_nRenObjFlags = CParticleManager::Instance()->GetRenderFlags();
	if (RenParams.dwFObjFlags & FOB_NEAREST)
		PartParams.m_nRenObjFlags.SetState(-1, FOB_SOFT_PARTICLE);
	if (m_nEmitterFlags & ePEF_Nowhere)
		PartParams.m_nRenObjFlags.SetState(-1, FOB_GLOBAL_ILLUMINATION | FOB_INSHADOW | FOB_SOFT_PARTICLE | FOB_MOTION_BLUR_PARTICLE);

	// Render relevant containers.
	for_all_ptrs (CParticleContainer, c, m_Containers)
	{
		m_Containers.PrefetchLink(m_Containers.next(c));
		PrefetchLine(m_Containers.next(c), 0);

		uint32 nContainerFlags = c->GetEnvironmentFlags();
		if (nContainerFlags & nRenFlags)
		{
			if ((nContainerFlags & nRenRequiredCheckFlags) == nRenRequiredFlags)
			{
				if (GetAge() <= c->GetContainerLife())
					c->Render( RenParams, PartParams );
			}
		}
	}

	if (m_nEmitterFlags & ePEF_Nowhere)
		RenderDebugInfo();
}

void CParticleEmitter::RenderDebugInfo()
{
	bool bSelected = IsEditSelected();
	if (TimeNotRendered() < 0.25f && (bSelected || (GetCVars()->e_ParticlesDebug & AlphaBit('b'))))
	{
		AABB const& bb = GetBBox();
		if (bb.IsReset())
			return;

		AABB bbDyn;
		GetDynamicBounds(bbDyn);

		CCamera const& cam = GetRenderer()->GetCamera();
		ColorF color(1,1,1,1);

		// Compute label position, in bb clipped to camera.
		Vec3 vPos = GetLocation().t;
		Vec3 vLabel = vPos;

		// Clip to cam frustum.
		float fBorder = 1.f; 
		for (int i = 0; i < FRUSTUM_PLANES; i++)
		{
			Plane const& pl = *cam.GetFrustumPlane(i);
			float f = pl.DistFromPlane(vLabel) + fBorder;
			if (f > 0.f)
				vLabel -= pl.n * f;
		}

		Vec3 vDist = vLabel - cam.GetPosition();
		vLabel += (cam.GetViewdir() * (vDist * cam.GetViewdir()) - vDist) * 0.1f;
		vLabel.CheckMax(bbDyn.min);
		vLabel.CheckMin(bbDyn.max);

		SParticleCounts counts;
		GetCounts(counts);
		float fPixToScreen = 1.f / ((float)GetRenderer()->GetWidth() * (float)GetRenderer()->GetHeight());

		if (!bSelected)
		{
			// Randomize hue by effect.
			color = CryStringUtils::CalculateHash(GetName());
			color.r = color.r * 0.5f + 0.5f;
			color.g = color.g * 0.5f + 0.5f;
			color.b = color.b * 0.5f + 0.5f;

			// Modulate alpha by screen-space bounds extents.
			int aiOut[4];
			cam.CalcScreenBounds(aiOut, &bbDyn, GetRenderer()->GetWidth(), GetRenderer()->GetHeight(), 0.f, 0.f);
			float fCoverage = (aiOut[2] - aiOut[0]) * (aiOut[3] - aiOut[1]) * fPixToScreen;

			// And by pixel and particle counts.
			float fWeight = sqrt_fast_tpl(fCoverage * counts.ParticlesRendered * counts.PixelsRendered * fPixToScreen);

			color.a = clamp_tpl(fWeight, 0.2f, 1.f);
		}

		IRenderAuxGeom* pRenAux = GetRenderer()->GetIRenderAuxGeom();
		pRenAux->SetRenderFlags(SAuxGeomRenderFlags());

		stack_string sLabel = GetName();
		sLabel += stack_string().Format(" P=%.0f F=%.3f S/D=",
			counts.ParticlesRendered,
			counts.PixelsRendered * fPixToScreen);
		if (counts.DynamicBoundsVolume > 0.f)
		{
			sLabel += stack_string().Format("%.2f", counts.StaticBoundsVolume / counts.DynamicBoundsVolume);
			if (counts.ErrorBoundsVolume > 0.f)
				sLabel += stack_string().Format(" E/D=%3f", counts.ErrorBoundsVolume / counts.DynamicBoundsVolume);
		}
		else
			sLabel += "~0";

		if (counts.ParticlesCollideTest)
			sLabel += stack_string().Format(" Col=%.0f/%.0f", counts.ParticlesCollideHit, counts.ParticlesCollideTest);
		if (counts.ParticlesClip)
			sLabel += stack_string().Format(" Clip=%.0f", counts.ParticlesClip);


		ColorF colLabel = color;
		if (!bb.ContainsBox(bbDyn))
		{
			// Tint red.
			colLabel.r = 1.f;
			colLabel.g *= 0.5f;
			colLabel.b *= 0.5f;
			colLabel.a = (color.a + 1.f) * 0.5f;
		}
		else if (m_nEnvFlags & EFF_DYNAMIC_BOUNDS)
		{
			// Tint yellow.
			colLabel.r = 1.f;
			colLabel.g = 1.f;
			colLabel.b *= 0.5f;
		}

		GetRenderer()->DrawLabelEx( vLabel, 1.5f, (float*)&colLabel, true, true, sLabel );

		// Compare static and dynamic boxes.
		if (!bbDyn.IsReset())
		{
			if (bbDyn.min != bb.min || bbDyn.max != bb.max)
			{
				// Separate dynamic BB.
				// Color outlying points bright red, draw connecting lines.
				ColorF colorGood = color * 0.8f;
				ColorF colorBad = colLabel;
				colorBad.a = colorGood.a;
				Vec3 vStat[8], vDyn[8];
				ColorB clrDyn[8];
				for (int i = 0; i < 8; i++)
				{
					vStat[i] = Vec3( i&1 ? bb.max.x : bb.min.x, i&2 ? bb.max.y : bb.min.y, i&4 ? bb.max.z : bb.min.z );
					vDyn[i] = Vec3( i&1 ? bbDyn.max.x : bbDyn.min.x, i&2 ? bbDyn.max.y : bbDyn.min.y, i&4 ? bbDyn.max.z : bbDyn.min.z );
					clrDyn[i] = bb.IsContainPoint(vDyn[i]) ? colorGood : colorBad;
					pRenAux->DrawLine(vStat[i], color, vDyn[i], clrDyn[i]);
				}
				
				// Draw dyn bb.
				if (bb.ContainsBox(bbDyn))
				{
					pRenAux->DrawAABB(bbDyn, false, colorGood, eBBD_Faceted);
				}
				else
				{
					pRenAux->DrawLine(vDyn[0], clrDyn[0], vDyn[1], clrDyn[1]);
					pRenAux->DrawLine(vDyn[0], clrDyn[0], vDyn[2], clrDyn[2]);
					pRenAux->DrawLine(vDyn[0], clrDyn[0], vDyn[4], clrDyn[4]);

					pRenAux->DrawLine(vDyn[1], clrDyn[1], vDyn[3], clrDyn[3]);
					pRenAux->DrawLine(vDyn[1], clrDyn[1], vDyn[5], clrDyn[5]);

					pRenAux->DrawLine(vDyn[2], clrDyn[2], vDyn[3], clrDyn[3]);
					pRenAux->DrawLine(vDyn[2], clrDyn[2], vDyn[6], clrDyn[6]);

					pRenAux->DrawLine(vDyn[3], clrDyn[3], vDyn[7], clrDyn[7]);

					pRenAux->DrawLine(vDyn[4], clrDyn[4], vDyn[5], clrDyn[5]);
					pRenAux->DrawLine(vDyn[4], clrDyn[4], vDyn[6], clrDyn[6]);

					pRenAux->DrawLine(vDyn[5], clrDyn[5], vDyn[7], clrDyn[7]);

					pRenAux->DrawLine(vDyn[6], clrDyn[6], vDyn[7], clrDyn[7]);
				}
			}
		}

		pRenAux->DrawAABB(bb, false, color, eBBD_Faceted);
	}
}

void CParticleEmitter::SerializeState(TSerialize ser)
{
	// Time values.
	ser.Value("Age", m_fAge);
	ser.Value("EndAge", m_fEndAge);

	// Spawn params.
	ser.Value("SizeScale", m_SpawnParams.fSizeScale);
	ser.Value("SpeedScale", m_SpawnParams.fSpeedScale);
	ser.Value("CountScale", m_SpawnParams.fCountScale);
	ser.Value("CountPerUnit", m_SpawnParams.bCountPerUnit);
	ser.Value("PulsePeriod", m_SpawnParams.fPulsePeriod);
}

void CParticleEmitter::GetMemoryUsage( ICrySizer* pSizer ) const
{
	m_Containers.GetMemoryUsage(pSizer);
	m_PhysEnviron.GetMemoryUsage(pSizer);
}

bool CParticleEmitter::UpdateStreamableComponents(float fImportance, Matrix34A & objMatrix, IRenderNode * pRenderNode, float fEntDistance, bool bFullUpdate, int nLod)
{
	FUNCTION_PROFILER_3DENGINE;

	CObjManager* pObjManager = Get3DEngine()->m_pObjManager;
	IRenderer* pRenderer = GetRenderer();
	for_all_ptrs (const CParticleContainer, c, m_Containers)
	{
		ResourceParticleParams const& params = c->GetParams();
		if (params.pStatObj)
		{
			static_cast<CStatObj*>(params.pStatObj.get())->UpdateStreamableComponents(fImportance, objMatrix, pRenderNode, fEntDistance, bFullUpdate, nLod);
		}

		if (params.nEnvFlags & REN_SPRITE)
		{
			ITexture* pTexture = pRenderer->EF_GetTextureByID(params.nTexId);
			if(pTexture)
			{
				const float minMipFactor = sqr(fEntDistance) / (params.GetFullTextureArea() + 1e-6f);
				pRenderer->EF_PrecacheResource(pTexture, minMipFactor, 0.f, bFullUpdate ? FPR_SINGLE_FRAME_PRIORITY_UPDATE : 0, GetObjManager()->m_nUpdateStreamingPrioriryRoundId);
			}
		}

		if(CMatInfo * pMatInfo = (CMatInfo *)(IMaterial*)params.pMaterial)
			pMatInfo->PrecacheMaterial(fEntDistance, NULL, bFullUpdate);
	}

	return true;
}

bool CParticleEmitter::NeedsNearestCubemapUpdate(uint16 & nCachedId) const
{
	if (CParticleManager::Instance()->GetRenderFlags() & (m_nRenObjFlags & OS_DIFFUSE_CUBEMAP))
	{
		static const int CUBEMAP_UPDATE_INTERVAL = 100;
		static int nGlobalLastUpdate = -1;

		nCachedId = m_nNearestCubemapId;
		const int nFrameId = GetFrameID();
		if (nGlobalLastUpdate != nFrameId			// update only 1 emitter per frame (spread updating to different frames)
			&& (nFrameId - m_nLastCubemapUpdateFrameId > CUBEMAP_UPDATE_INTERVAL))   // update only once per several frames
		{
			nGlobalLastUpdate = nFrameId;
			return true;
		}
	}
	else
	{
		nCachedId = 0;
	}
	return false;
}

void CParticleEmitter::CacheNearestCubemap(uint16 nCMTexId)
{
	m_nNearestCubemapId = nCMTexId;
	m_nLastCubemapUpdateFrameId = GetFrameID();
}

void CParticleEmitter::CollectSpuUsage( VecSpuUsageT &vecSpuUsage )
{
	for_all_ptrs (CParticleContainer, c, m_Containers)
		c->CollectSpuUsage( vecSpuUsage );
}

void CParticleEmitter::OffsetPosition(const Vec3& delta)
{
#ifdef SEG_WORLD
	if (m_pRNTmpData) m_pRNTmpData->OffsetPosition(delta);
	m_Target.vTarget += delta;
	m_bbWorld.Move(delta);
	CParticleSource::OffsetPosition(delta);
	//for_all_ptrs (CParticleContainer, c, m_Containers)
	//	c->OffsetPosition(delta);
#endif
}

#include UNIQUE_VIRTUAL_WRAPPER(IParticleEmitter)


